package close.mazegame.pathfinding;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Random;

import close.mazegame.level.Level;
import close.mazegame.tile.Tile;

import com.badlogic.gdx.math.Vector2;

// Calculates the shortest distance to its target at all points on the map.
// Entities can then move up/down the gradient of the path[][] field to get to/from the target

public class Pathfinder {

	private Level level;
	private int path[][];

	private int width;
	private int height;

	private float xTarget = 0.0f;
	private float yTarget = 0.0f;

	private Random random = new Random();

	private ArrayList<Point> toDo;

	public Pathfinder(Level level) {
		this.level = level;
		this.width = level.getWidth();
		this.height = level.getHeight();

		path = new int[width][height];
		toDo = new ArrayList<Point>();
	}
	
	public void setTarget(Vector2 target) {
		this.setTarget(target.x, target.y);
	}

	public void setTarget(float xTarget, float yTarget) {
		this.xTarget = xTarget;
		this.yTarget = yTarget;
	}

	public void update() {
		path = new int[width][height];

		path[(int) xTarget][(int) yTarget] = 1;
		addNeighbours(new Point((int) xTarget, (int) yTarget));
		while (toDo.size() > 0) {
			Point next = toDo.get(0);
			setDistance(next);
			addNeighbours(next);
			toDo.remove(next);
		}
	}
	
	public boolean isValid(int x, int y) {
		if (getDistance(x, y) >= width * height || getDistance(x, y) <= 0) {
			return false;
		} else {
			return true;
		}
	}

	public boolean isValid(Point pos) {
		return isValid(pos.x, pos.y);
	}

	private void setDistance(Point pos) {
		int x = pos.x;
		int y = pos.y;
		if (!level.isSolid(x - 1, y)) {
			if (getDistance(x - 1, y) != 0) {
				path[x][y] = getDistance(x - 1, y) + 1;
				return;
			}
		}

		if (!level.isSolid(x + 1, y)) {
			if (getDistance(x + 1, y) != 0) {
				path[x][y] = getDistance(x + 1, y) + 1;
				return;
			}
		}

		if (!level.isSolid(x, y - 1)) {
			if (getDistance(x, y - 1) != 0) {
				path[x][y] = getDistance(x, y - 1) + 1;
				return;
			}
		}

		if (!level.isSolid(x, y + 1)) {
			if (getDistance(x, y + 1) != 0) {
				path[x][y] = getDistance(x, y + 1) + 1;
				return;
			}
		}
	}

	private void addNeighbours(Point pos) {
		int x = pos.x;
		int y = pos.y;
		if (!level.isSolid(x - 1, y))
			if (getDistance(x - 1, y) == 0)
				toDo.add(new Point(x - 1, y));

		if (!level.isSolid(x + 1, y))
			if (getDistance(x + 1, y) == 0)
				toDo.add(new Point(x + 1, y));

		if (!level.isSolid(x, y - 1))
			if (getDistance(x, y - 1) == 0)
				toDo.add(new Point(x, y - 1));

		if (!level.isSolid(x, y + 1))
			if (getDistance(x, y + 1) == 0)
				toDo.add(new Point(x, y + 1));
	}

	public int getDistance(int x, int y) {
		if (x < 0 || x >= width || y < 0 || y >= height)
			return width * height; // Should be higher than any valid path
		else
			return path[x][y];
	}

	public int getDistance(Point pos) {
		return getDistance(pos.x, pos.y);
	}

	public int getMaxDistance() {
		int maxDist = 0;
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < width; j++) {
				if (path[i][j] > maxDist)
					maxDist = path[i][j];
			}
		}
		return maxDist;
	}

	public Vector2 getMaxDistancePosition() {
		int maxDist = 0;
		Vector2 pos = new Vector2(0, 0);
		for (int i = 0; i < width; i++) {
			for (int j = 0; j < height; j++) {
				if (path[i][j] > maxDist) {
					maxDist = path[i][j];
					pos.x = i + Tile.SIZE / 2;
					pos.y = j + Tile.SIZE / 2;
				}
			}
		}
		return pos;
	}

	public Vector2 getNearer(Point pos, boolean ignoreDiagonals) {
		
		if (pos.x == (int) xTarget && pos.y == (int) yTarget) {
			return new Vector2(xTarget, yTarget);
		}

		Vector2 newPos = new Vector2(0, 0);
		int minNeighbour = width * height;
		int n = 1;
		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {
				if (ignoreDiagonals && i != 0 && j != 0) {
					continue;
				}
				if (isValid(pos.x + i, pos.y + j)) {
					if (getDistance(pos.x + i, pos.y + j) < minNeighbour) {
						minNeighbour = getDistance(pos.x + i, pos.y + j);
						newPos.x = pos.x + i;
						newPos.y = pos.y + j;
					} else if (getDistance(pos.x + i, pos.y + j) == minNeighbour && random.nextInt(++n) == 0) {
						newPos.x = pos.x + i;
						newPos.y = pos.y + j;
					}
				}
			}
		}
		newPos.x += Tile.SIZE / 2.0;
		newPos.y += Tile.SIZE / 2.0;
		return newPos;
	}

	public Vector2 getFurther(Point pos, boolean ignoreDiagonals) {
		int x = pos.x;
		int y = pos.y;
		Vector2 newPos = new Vector2(0, 0);
		int maxNeighbour = 0;
		int n = 1;
		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {
				if (ignoreDiagonals && i != 0 && j != 0) {
					continue;
				}
				if (isValid(pos.x + i, pos.y + j)) {
					if (getDistance(x + i, y + j) > maxNeighbour) {
						maxNeighbour = getDistance(x + i, y + j);
						newPos.x = x + i;
						newPos.y = y + j;
					} else if (getDistance(x + i, y + j) == maxNeighbour && random.nextInt(++n) == 0) {
						newPos.x = x + i;
						newPos.y = y + j;
					}
				}
			}
		}
		newPos.x += Tile.SIZE / 2.0;
		newPos.y += Tile.SIZE / 2.0;
		return newPos;
	}

	public ArrayList<Point> getNeighbours(Point pos, boolean ignoreDiagonals) {

		int x = (int) pos.x;
		int y = (int) pos.y;
		ArrayList<Point> neighbours = new ArrayList<Point>();
		for (int i = -1; i <= 1; i++) {
			for (int j = -1; j <= 1; j++) {
				if (i == 0 && j == 0) {
					continue;
				}
				if (ignoreDiagonals && i != 0 && j != 0) {
					continue;
				}
				if (isValid(pos.x + i, pos.y + j)) {
					neighbours.add(new Point(x + i, y + j));
				}
			}
		}
		return neighbours;
	}

}
